13. 其他 IO 流及线程

BufferedReader

BufferedReader 缓冲流中提供了对纯文本数据进行按行读取处理的方法 readLine, 方法返回的是读取到的字符串,如果没有读取到则返回 null。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.itguigu.com;

import java.io.BufferedReader;

import java.io.FileReader;
import java.io.IOException;

public class TestBufferReader {
public static void main(String[] args) throws IOException {
// 文本类型使用字符输入流
FileReader fileReader = new FileReader("resources/text.txt");
// 包装字符缓冲流, 利用字符缓冲流对文本按行读取
BufferedReader bufferedReader = new BufferedReader(fileReader);
String line;
while ((line = bufferedReader.readLine())!=null) {
System.out.println(line);
}
}
}

DataOoutputStream

可以将基本的 Java 数据类型写入输出流 DataInputStream 中。

Java 基本数据类型:8种

基本的 Java 数据类型:8 种 + String

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.io.IOException;

import org.junit.Test;

public class TestDataOutputStream {
@Test
public void save() throws IOException {
// 需求:将以下数据按照不同的数据类型进行保存,恢复时,也按照不同数据类型进行恢复
String name = "张三";
int age = 1000;
char gender = '男';
double salary = 100.1;

DataOutputStream dataOutputStream = new DataOutputStream(new FileOutputStream("resources/data.dat"));

dataOutputStream.writeUTF(name);
dataOutputStream.writeInt(age);
dataOutputStream.writeChar(gender);
dataOutputStream.writeDouble(salary);

dataOutputStream.close();
}
}

DataInputStream

可以从底层输入流中读取基本 Java 数据类型的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.itguigu.com;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

import org.junit.Test;

public class TestDataOutputStream {
@Test
public void read() throws IOException {
// 需求:将上面程序的 data.dat 文件读出

// 以 DataOutputStream 写的 需要使用 DataInputStream 读
DataInputStream dataInputStream = new DataInputStream(new FileInputStream("resources/data.dat"));

// 读取的顺序要和写的顺序一致
String name = dataInputStream.readUTF();
int readInt = dataInputStream.readInt();
char readChar = dataInputStream.readChar();
double readDouble = dataInputStream.readDouble();

System.out.println(name);
System.out.println(readInt);
System.out.println(readChar);
System.out.println(readDouble);

dataInputStream.close();
}
}

ObjectOutputStream

输出对象,可以称为序列化

1
2
3
4
5
6
7
8
@Test
public void save() throws IOException {
Employee employee = new Employee(1, "zhangsan", 100001);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("resources/employee.dat"));
// Employee 需要实现 Serializable 接口,否则会出现 NotSerializableException 错误
objectOutputStream.writeObject(employee);
objectOutputStream.close();
}

ObjectInputStream

读取对象,也可以称为反序列化

1
2
3
4
5
6
7
8
@Test
public void read() throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("resources/employee.dat"));
// 读取
System.out.println(objectInputStream.readObject());
// 输出对象,并调用了 toString 方法,结果为 Employee [id=1, name=zhangsan, salary=100001.0]
objectInputStream.close();
}

完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
package com.itguigu.com;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

import org.junit.Test;

public class TestObjectIO {
@Test
public void save() throws IOException {
Employee employee = new Employee(1, "zhangsan", 100001);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("resources/employee.dat"));
// Employee 需要实现 Serializable 接口,否则会出现 NotSerializableException 错误
objectOutputStream.writeObject(employee);
objectOutputStream.close();

}

@Test
public void read() throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("resources/employee.dat"));
// 读取
System.out.println(objectInputStream.readObject());
// 输出对象,并调用了 toString 方法,结果为 Employee [id=1, name=zhangsan, salary=100001.0]
objectInputStream.close();
}
}

// Serializable 接口是一个标识形接口,即没有抽象方法,只是标识一下
class Employee implements Serializable{
private int id;
private String name;
private double salary;

public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public Employee(int id, String name, double salary) {
super();
this.id = id;
this.name = name;
this.salary = salary;
}
public Employee() {
super();
}
@Override
public String toString() {
return "Employee [id=" + id + ", name=" + name + ", salary=" + salary + "]";
}
}

在实现 Serializable 接口时,最好需要加上 serialVersionID(序列化版本 ID),如果不加,那么只要类重新编译,原来序列化的数据就无法反序列化了,因为每次编译都会自动随机产生一个 serialVersionID。如果加了,那么就算类重新编译,serialVersionID 的值不变,原来序列化的数据仍然可以反序列化。

自定义异常中的 serialVersionUID

  1. 继承 Throwable 或它的子类
  2. 建议保留两个构造器
  3. 增加序列化版本 ID
  4. 自定义异常对象只能使用 throw 抛出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.itguigu.com;

public class MyException extends Exception{
// 加上序列化版本 ID
private static final long serialVersionUID = 7558865975137180878L;

public MyException() {
super();
}

public MyException(String message) {
super(message);
}
}

Serializable

在实现 Serializable 接口时,最好需要加上 serialVersionID(序列化版本 ID)。

如果一个对象中的某些属性不想被序列化,那么只需要在该属性的参数类型前面加上 transient 即可。

一个类的静态变量是不会序列化的,即序列化前后值不变。因为静态变量不是属于某个对象的,是所有对象共享的。而序列化对象保存的是对象的状态信息,是每个对象独立的信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
package com.itguigu.com;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

import org.junit.Test;

public class TestObjectIO {
@Test
public void save() throws IOException {
Employee employee = new Employee(1, "zhangsan", 100001);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("resources/employee.dat"));
objectOutputStream.writeObject(employee);
objectOutputStream.close();
}

@Test
public void read() throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("resources/employee.dat"));
System.out.println(objectInputStream.readObject());
objectInputStream.close();
}
}

class Employee implements Serializable{
// 只需加上一个 serialVersionUID 即可,值随便一个都行
private static final long serialVersionUID = 4356894757096033197L;
private int id;
private String name;
private transient double salary; // transient 代表此参数不想被序列化。

public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public Employee(int id, String name, double salary) {
super();
this.id = id;
this.name = name;
this.salary = salary;
}
public Employee() {
super();
}
@Override
public String toString() {
return "Employee [id=" + id + ", name=" + name + ", salary=" + salary + "]";
}
}

Externalizable

除了实现 Serializable 接口可以实现序列化以外,Externalizable 接口也可以实现。java.io.Externalizable 是继承自 Serializable 接口的。但是实现 Externalizable 接口后必须重写两个抽象方法,writeExternal 和 readExternal。Externalizable 不同于 Serializable,Externalizable 可以自定义序列化和反序列化的属性,顺序等,而 Serializable 序列化的顺序是默认的,且 static 和 transient 是不能序列化的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
package com.itguigu.com;

import java.io.Externalizable;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;

import org.junit.Test;

public class TestExternalizable {
@Test
public void save() throws IOException {
// 对象序列化
Student23 student23 = new Student23("zhangsan", 23);
student23.setSalary(890890.1); // 就算是 static 属性也能序列化

ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("resources/obj.dat"));
objectOutputStream.writeObject(student23);
objectOutputStream.close();
}

@Test
public void read() throws IOException, ClassNotFoundException {
// 对象反序列化
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("resources/obj.dat"));
System.out.println(objectInputStream.readObject());
objectInputStream.close();
}
}

class Student23 implements Externalizable{
private String name;
private transient int age;
private static double salary=30000;

public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public static double getSalary() {
return salary;
}
public static void setSalary(double salary) {
Student23.salary = salary;
}
@Override
public String toString() {
return name + "," + age + "," + salary;
}
public Student23(String name, int age) {
super();
this.name = name;
this.age = age;
}
public Student23() {
super();
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
// 自定义序列化顺序和字段, 就算是 static 的属性和 transient 的属性都可以序列化
out.writeUTF(name);
out.writeInt(age);
out.writeDouble(salary);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
// 自定义反序列化顺序和字段
name = in.readUTF();
age = in.readInt();
salary = in.readDouble();
}
}

System 中的 IO 流

  1. System.in, 类型为 InputStream
  2. System.out, 类型为 PrintStream
  3. System.err, 类型为 PrintStream

进程和线程

进程是操作系统分配资源的最小单位,线程是 CPU 调度资源的最小单位。多个线程之间存在内存共享,共享同一个进程的资源。因为 CPU 给每个线程分配到的时间非常短,所以用户没有感觉,就如同时运行一样。

JVM运行时内存:方法区,堆,栈(虚拟机栈,本地方法栈),程序计数器。其中堆内存和方法区的内存是共享的,栈和程序计数器是每个线程独立的。

堆:存放对象。即实例是线程共享的。

方法区:存放类信息,常量,静态相关信息等。即常量,静态变量是线程共享的。

栈:存放局部变量。即局部变量是每个线程都是独立的。

Thread

实现多线程的方法之一就是继承 java.lang.Thread 类,并且重写 run 方法。

步骤:

  1. 声明线程类,继承 Thread 类
  2. 重写 run 方法
  3. 创建线程对象
  4. 启动线程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.itguigu.com;

public class TestThread {
public static void main(String[] args) {
// 创建线程对象
MyThread myThread = new MyThread();
// 启动线程
myThread.start();
for (int i = 1; i < 100; i+=2) {
System.out.println("奇数:" + i);
}
}
}

class MyThread extends Thread{
// 重写 Thread 中的 run 方法
@Override
public void run() {
for (int i = 2; i < 100; i+=2) {
System.out.println("偶数:" + i);
}
}
}
常见方法
  1. 构造器。共有三种构造器,分别是 Thread()Thread(Runnable target)Thread(Runnable target, String name)
  2. getName()/setName(String name),获取和设置线程名称。默认线程名称是 Thread-编号
  3. Thread.currentThread(),获取当前线程对象。是静态方法
  4. getPriority,setPriority(int newPriority),获取和设置线程优先级。Java 中线程优先级一共有 10 个等级,从1到10,数字越大则代表等级越高,Thread 类中预定义了三个最基本的优先级,MIN_PRIORITY:1, NORM_PRIORITY:5, MAX_PRIORITY: 10。如果优先级不在 1-10 的范围内,则会抛出异常。
  5. Thread.sleep(int xx),线程睡眠。是静态方法
  6. join(), 线程加塞,或者插队。需要等到加塞进来的线程执行结束后,才能继续之前的线程。
  7. Thread.yield。暂停当前线程,让出本次 CPU 的资源。是静态方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.itguigu.com;

public class TestThreadMethod {
public static void main(String[] args) throws InterruptedException {
CusThread cusThread = new CusThread();
System.out.println(cusThread.getName()); // Thread-0
cusThread.setPriority(5); // 设置优先级为 5
cusThread.start();

CusThread cusThread2 = new CusThread();
cusThread2.setName("new Name"); // 设置新名字
System.out.println(cusThread2.getName()); // new Name
cusThread2.start();

for (int i = 0; i < 10; i++) {
if (i==5) {
cusThread.join(); // main 线程被 cusThread 加塞了,只有等待 cusThread 结束才能继续执行 main
// Thread.yield();
}
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}

class CusThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000); // sleep 1s
System.out.println(getName() + ":" + i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Runnable

实现多线程还可以实现 java.lang.Runnable 接口,并重写 run 方法。Runnable 中没有 start 方法,只有 Thread 中有 start 方法,所以必须要 Thread 类的对象才能启动多线程。

步骤:

  1. 声明线程类,实现 Runnable 接口
  2. 重写 run 方法
  3. 创建线程对象
  4. 创建 Thread 对象
  5. 启动线程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.itguigu.com;

public class testRunnable {
public static void main(String[] args) {
// 创建线程对象
myThread2 myThread2 = new myThread2();
// Thread 封装线程对象
Thread thread = new Thread(myThread2);
// 启动线程
thread.start();

for (int i = 1; i < 100; i+=2) {
System.out.println("奇数:" + i);
}
}
}

class myThread2 implements Runnable{

@Override
public void run() {
for (int i = 2; i < 100; i+=2) {
System.out.println("偶数:" + i);
}
}
}